using System;
using System.Collections.Generic;
using System.Linq;
using Engine;
using Engine.Graphics;

namespace RuthlessConquest {
    public class PlanetsModule : Module {
        public DynamicArray<Planet> publicPlanets = [];

        //public DynamicArray<Satellite> publicSatellites = [];
        public DynamicArray<Planet> TmpPlanetsList = []; //Selected and reachable planets
        public HashSet<Planet>[] FactionOwnPlanets = new HashSet<Planet>[7];
        public int[] FactionOwnSatelliteCount = new int[7];
        public HashSet<Planet> SpecialPlanets = [];

        public ReadOnlyList<Planet> Planets => new(publicPlanets);
        //public ReadOnlyList<Satellite> Satellites => new(publicSatellites);

        public PlanetsModule(Game game) : base(game) {
            /*game.EntityAdded += delegate(Entity e) {
                if (e is Planet item) {
                    publicPlanets.Add(item);
                }
                else if (e is Satellite item2) {
                    if (publicSatellites.TryGetValue(item2.Planet, out HashSet<Satellite> satellites)) {
                        satellites.Add(item2);
                    }
                    else {
                        publicSatellites.Add(item2.Planet, [item2]);
                    }
                }
            };
            game.EntityRemoved += delegate(Entity e) {
                if (e is Planet item3) {
                    publicPlanets.Remove(item3);
                }
                else if (e is Satellite item4) {
                    if (publicSatellites.TryGetValue(item4.Planet, out HashSet<Satellite> satellites2)) {
                        satellites2.Remove(item4);
                    }
                }
            };*/
            for (int i = 0; i < 7; i++) {
                FactionOwnPlanets[i] = [];
            }
        }

        public Planet PickPlanet(Vector2 screenPoint) {
            Vector2 v = Vector2.Transform(screenPoint, CameraModule.ScreenToWorldMatrix);
            foreach (Planet planet in Planets) {
                if (Vector2.Distance(v, new Vector2(planet.Position)) <= planet.Radius * 1.4f) {
                    return planet;
                }
            }
            return null;
        }

        public void Step() {
            foreach (Planet planet in Planets) {
                planet.Step();
            }
            foreach (Planet planet in Planets) {
                foreach (Satellite satellite in planet.Satellites) {
                    satellite.Step();
                }
            }
        }

        public void Draw(Color colorTransform) {
            foreach (Planet planet in Planets) {
                planet.ShowRangeDisc = false;
                planet.ShowHighlight = false;
            }
            TmpPlanetsList.Clear();
            TmpPlanetsList.AddRange(Planets.Where(p => p.IsSelected));
            FindReachablePlanetsSet(TmpPlanetsList);
            foreach (Planet tmpPlanets in TmpPlanetsList) {
                tmpPlanets.ShowHighlight = true;
                if (tmpPlanets.Faction == PlayersModule.ControllingPlayer.Faction) {
                    tmpPlanets.ShowRangeDisc = true;
                }
            }
            foreach (Planet planet2 in Planets) {
                planet2.DrawPlanet(colorTransform);
            }
            foreach (Planet planet in Planets) {
                foreach (Satellite satellite in planet.Satellites) {
                    satellite.Draw(colorTransform);
                }
            }
            foreach (Planet planet3 in Planets) {
                planet3.DrawOverlays(colorTransform);
            }
            CameraModule.PrimitivesRenderer.Flush(CameraModule.WorldToScreenMatrix * PrimitivesRenderer2D.ViewportMatrix());
        }

        public void FindReachablePlanetsSet(DynamicArray<Planet> set) {
            if (PlayersModule.ControllingPlayer == null) {
                return;
            }
            for (int i = 0; i < set.Count; i++) {
                Planet planet = set[i];
                if (planet.Faction != PlayersModule.ControllingPlayer.Faction) {
                    continue;
                }
                foreach (Planet planet2 in Planets) {
                    if (ShipsModule.TestReachability(planet2.Position, planet.Position)
                        && !set.Contains(planet2)) {
                        set.Add(planet2);
                    }
                }
            }
        }

        public int GetFactionPlanetsCount(Faction faction) => FactionOwnPlanets[(int)faction].Count;

        public int GetFactionProductionPercentage(Faction faction) {
            int num = 0;
            int num2 = 0;
            foreach (Planet planet in Planets) {
                if (planet.ProductionPeriod > 0) {
                    if (planet.Faction == faction) {
                        num += 10000 / planet.ProductionPeriod;
                    }
                    num2 += 10000 / planet.ProductionPeriod;
                }
            }
            if (num2 <= 0) {
                return 0;
            }
            return 100 * num / num2;
        }

        public void CreatePlanets() {
            List<StructPlanet> litePlanets = GeneratePlanetsList(
                Game.CreationParameters.Seed,
                Game.CreationParameters.PlanetsCount,
                Game.CreationParameters.SpecialPlanetsCount,
                Game.CreationParameters.NeutralsPopulationFactor,
                Game.CreationParameters.ShipRange,
                Game.CreationParameters.Factions
            );
            foreach (StructPlanet litePlanet in litePlanets) {
                Planet planet = new(litePlanet.Position, litePlanet.Faction, litePlanet.SizeClass, litePlanet.ShipsCount, litePlanet.IsSpecial);
                Game.AddEntity(planet);
                if (litePlanet.SatellitesCount > 0) {
                    Game.AddEntity(new Satellite(planet, -1));
                    if (litePlanet.SatellitesCount > 1) {
                        Game.AddEntity(new Satellite(planet, -1));
                    }
                }
            }
        }

        public static Point2[] Shifts { get; } = GetShifts();

        public static List<StructPlanet> GeneratePlanetsList(int seed,
            int planetsCount,
            int specialPlanetsCount,
            int neutralsPopulationFactor,
            int shipRange,
            DynamicArray<Faction> factions) {
            Random random = new(MathUtils.Hash(seed));
            Point2 worldSize = Game.WorldSize;
            DynamicArray<Rectangle> forbiddenUISpaces = [
                new(0, 0, worldSize.X, worldSize.Y / 12), new(0, worldSize.Y * 15 / 16, worldSize.X, worldSize.Y / 16)
            ];
            List<Point2> positions = [];
            List<Rectangle> rectangles = new(8);
            Rectangle rectangle1 = new(0, 0, 3200, 1800);
            Rectangle rectangle2 = new(worldSize.X - 3200, 0, 3200, 1800);
            Rectangle rectangle3 = new(worldSize.X - 3200, worldSize.Y - 1800, 3200, 1800);
            Rectangle rectangle4 = new(0, worldSize.Y - 1800, 3200, 1800);
            for (int k = 0; k < 900; k++) {
                rectangles.Clear();
                int num = random.Int(1, 2);
                for (int l = 0; l < num; l++) {
                    Rectangle rectangle = new(
                        worldSize.X * random.Int(0, 70) / 100,
                        worldSize.Y * random.Int(0, 70) / 100,
                        worldSize.X * random.Int(25, 32) / 100,
                        worldSize.Y * random.Int(25, 32) / 100
                    );
                    rectangles.Add(rectangle);
                }
                for (int m = 0; m < 5; m++) {
                    Rectangle rectangle = new(
                        worldSize.X * random.Int(0, 80) / 100,
                        worldSize.Y * random.Int(0, 80) / 100,
                        worldSize.X * random.Int(12, 22) / 100,
                        worldSize.Y * random.Int(12, 22) / 100
                    );
                    rectangles.Add(rectangle);
                }
                bool flag1 = false;
                bool flag2 = false;
                bool flag3 = false;
                bool flag4 = false;
                foreach (Rectangle r in rectangles) {
                    flag1 |= r.Intersection(rectangle1);
                    flag2 |= r.Intersection(rectangle2);
                    flag3 |= r.Intersection(rectangle3);
                    flag4 |= r.Intersection(rectangle4);
                }
                if ((!flag1 && !flag3)
                    || (!flag2 && !flag4)) {
                    break;
                }
            }
            const int testR = 550;
            int shipRange1 = shipRange * 99 / 100;
            for (int j = 0; j < planetsCount; j++) {
                for (int i = 0; i < 10000; i++) {
                    int requiredDistance = Math.Min(Math.Max(7000 - i, 1300), shipRange1 - 300);
                    Point2 position = new(random.Int(testR, worldSize.X - testR), random.Int(testR, worldSize.Y - testR));
                    Rectangle testRectangle = new(position.X - testR, position.Y - testR, 2 * testR, 2 * testR);
                    if (forbiddenUISpaces.Any(r => r.Intersection(testRectangle))
                        || (i <= 9500 && rectangles.Any(r => r.Intersection(testRectangle)))) {
                        continue;
                    }
                    if (positions.Count == 0) {
                        positions.Add(position);
                        break;
                    }
                    int minDistance = positions.Min(p => IntMath.ApproxDistance(p, position));
                    if (minDistance >= requiredDistance
                        && minDistance <= shipRange1) {
                        positions.Add(position);
                        break;
                    }
                }
            }
            int num7 = int.MaxValue;
            int num8 = int.MaxValue;
            int num9 = int.MinValue;
            int num10 = int.MinValue;
            foreach (Point2 item2 in positions) {
                num7 = Math.Min(num7, item2.X);
                num8 = Math.Min(num8, item2.Y);
                num9 = Math.Max(num9, item2.X);
                num10 = Math.Max(num10, item2.Y);
            }
            Point2 point = new Point2(num7 + num9, num8 + num10) / 2;
            foreach (Point2 item in Shifts) {
                Point2 actualShift = worldSize / 2 - point + item;
                List<Point2> source = positions.Select(p => p + actualShift).ToList();
                if (!source.Any(p => {
                            Rectangle rectangle = new(p.X - testR, p.Y - testR, 2 * testR, 2 * testR);
                            return forbiddenUISpaces.Any(r => r.Intersection(rectangle));
                        }
                    )) {
                    positions = source;
                    break;
                }
            }
            HashSet<Point2> playerPositions = [
                positions.OrderByDescending(p => IntMath.ApproxDistance(p, worldSize / 2)).ToArray()[random.Int(0, positions.Count / 3)]
            ];
            for (int num11 = 1; num11 < factions.Count; num11++) {
                int num12 = positions.Max(p1 => playerPositions.Min(p2 => IntMath.ApproxDistance(p1, p2)));
                int num13 = random.Int(num12 * 8 / 10, num12);
                foreach (Point2 position2 in positions) {
                    if (!playerPositions.Contains(position2)
                        && playerPositions.Min(p => IntMath.ApproxDistance(p, position2)) >= num13) {
                        playerPositions.Add(position2);
                        break;
                    }
                }
            }
            HashSet<Point2> specialPositions = [];
            for (int num14 = 0; num14 < specialPlanetsCount; num14++) {
                Point2? point2 = null;
                int num15 = 0;
                foreach (Point2 position3 in positions) {
                    if (!playerPositions.Contains(position3)
                        && !specialPositions.Contains(position3)) {
                        int num16 = playerPositions.Min(p => IntMath.ApproxDistance(p, position3));
                        int x2 = specialPositions.Count > 0 ? specialPositions.Min(p => IntMath.ApproxDistance(p, position3)) : int.MaxValue;
                        int num17 = MathUtils.Min(2 * num16, x2);
                        if (!point2.HasValue
                            || num17 > num15) {
                            point2 = position3;
                            num15 = num17;
                        }
                    }
                }
                if (point2.HasValue) {
                    specialPositions.Add(point2.Value);
                }
            }
            int num18 = 0;
            List<StructPlanet> result = new(positions.Count);
            foreach (Point2 position in positions) {
                Faction faction = playerPositions.Contains(position) ? factions[num18++] : Faction.Neutral;
                bool isSpecial = specialPositions.Contains(position);
                int sizeClass;
                int shipsCount;
                if (faction != Faction.Neutral) {
                    sizeClass = 2;
                    shipsCount = 100;
                }
                else {
                    int num20 = IntMath.Length(100 * (position - worldSize / 2) / (IntMath.Length(worldSize) / 2));
                    int num21 = num20 < 25 ? 700 :
                        num20 >= 45 ? 70 : 350;
                    sizeClass = random.Int(1000) < num21 ? 2 : random.Int(0, 1);
                    if (isSpecial) {
                        shipsCount = random.Int(60, 100);
                    }
                    else {
                        int min = 1 + 9 * sizeClass;
                        int max = 15 + 9 * sizeClass;
                        shipsCount = MathUtils.Max(random.Int(min, max) * neutralsPopulationFactor / 100, 1);
                    }
                }
                /*Planet planet = new(position, faction, num22, shipsCount, flag6);
                Game.AddEntity(planet);
                if (flag6) {
                    Game.AddEntity(new Satellite(planet, -1));
                    if (random.Int() % 5 == 0) {
                        Game.AddEntity(new Satellite(planet, -1));
                    }
                }*/
                int satellitesCount = isSpecial ? random.Int() % 5 == 0 ? 2 : 1 : 0;
                result.Add(new StructPlanet(position, faction, sizeClass, shipsCount, isSpecial, satellitesCount));
            }
            return result;
        }

        public static Point2[] GetShifts() {
            Point2[] shifts = new Point2[441];
            for (int i = 0; i <= 20; i++) {
                for (int j = 0; j <= 20; j++) {
                    shifts[i * 21 + j] = new Point2(i * 100 - 1000, j * 100 - 1000);
                }
            }
            shifts = shifts.OrderBy(s => 1000L * IntMath.LengthSquared(s) + shifts.IndexOf(s)).ToArray();
            return shifts;
        }
    }
}